package com.baidu.iit.pxp.behavior;

import com.baidu.iit.pvm.ActivityException;
import com.baidu.iit.pvm.ActivityIllegalArgumentException;
import com.baidu.iit.pvm.PvmEventConstacts;
import com.baidu.iit.pvm.delegate.*;
import com.baidu.iit.pvm.delegate.SubProcessActivityBehavior;
import com.baidu.iit.pvm.process.ActivityImpl;
import com.baidu.iit.pvm.runtime.AtomicOperation;
import com.baidu.iit.pvm.runtime.InterpretableExecution;
import com.baidu.iit.pxp.BpmnError;
import com.baidu.iit.pxp.delegate.Expression;
import com.baidu.iit.pxp.invocation.ExecutionListenerInvocation;
import com.baidu.iit.pxp.util.ErrorPropagation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * User: huangweili
 * Date: 14-4-28
 * Time: 下午3:06
 */
public abstract class MultiInstanceActivityBehavior extends FlowNodeActivityBehavior
        implements CompositeActivityBehavior, SubProcessActivityBehavior {

    protected static final Logger LOGGER = LoggerFactory.getLogger(MultiInstanceActivityBehavior.class);

    protected final String NUMBER_OF_INSTANCES = "nrOfInstances";
    protected final String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";
    protected final String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";

    protected ActivityImpl activity;
    protected AbstractBpmnActivityBehavior innerActivityBehavior;
    protected Expression loopCardinalityExpression;
    protected Expression completionConditionExpression;
    protected Expression collectionExpression;
    protected String collectionVariable;
    protected String collectionElementVariable;
    protected String collectionElementIndexVariable = "loopCounter";


    public MultiInstanceActivityBehavior(ActivityImpl activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
        this.activity = activity;
        setInnerActivityBehavior(innerActivityBehavior);
    }

    public void execute(ActivityExecution execution) throws Exception {
        if (getLocalLoopVariable(execution, getCollectionElementIndexVariable()) == null) {
            try {
                createInstances(execution);
            } catch (BpmnError error) {
                ErrorPropagation.propagateError(error, execution);
            }
        } else {
            innerActivityBehavior.execute(execution);
        }
    }

    protected abstract void createInstances(ActivityExecution execution) throws Exception;

    public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception {
        innerActivityBehavior.signal(execution, signalName, signalData);
    }

    public void lastExecutionEnded(ActivityExecution execution) {
       // ScopeUtil.createEventScopeExecution((ExecutionEntity) execution);
        leave(execution);
    }

    public void completing(DelegateExecution execution, DelegateExecution subProcessInstance) throws Exception {
    }

    public void completed(ActivityExecution execution) throws Exception {
        leave(execution);
    }


    @SuppressWarnings("rawtypes")
    protected int resolveNrOfInstances(ActivityExecution execution) {
        int nrOfInstances = -1;
        if (loopCardinalityExpression != null) {
            nrOfInstances = resolveLoopCardinality(execution);
        } else if (collectionExpression != null) {
            Object obj = collectionExpression.getValue(execution);
            if (!(obj instanceof Collection)) {
                throw new ActivityIllegalArgumentException(collectionExpression.getExpressionText() + "' didn't resolve to a Collection");
            }
            nrOfInstances = ((Collection) obj).size();
        } else if (collectionVariable != null) {
            Object obj = execution.getVariable(collectionVariable);
            if (obj == null) {
                throw new ActivityIllegalArgumentException("Variable " + collectionVariable + " is not found");
            }
            if (!(obj instanceof Collection)) {
                throw new ActivityIllegalArgumentException("Variable " + collectionVariable + "' is not a Collection");
            }
            nrOfInstances = ((Collection) obj).size();
        } else {
            throw new ActivityIllegalArgumentException("Couldn't resolve collection expression nor variable reference");
        }
        return nrOfInstances;
    }

    @SuppressWarnings("rawtypes")
    protected void executeOriginalBehavior(ActivityExecution execution, int loopCounter) throws Exception {
        if (usesCollection() && collectionElementVariable != null) {
            Collection collection = null;
            if (collectionExpression != null) {
                collection = (Collection) collectionExpression.getValue(execution);
            } else if (collectionVariable != null) {
                collection = (Collection) execution.getVariable(collectionVariable);
            }

            Object value = null;
            int index = 0;
            Iterator it = collection.iterator();
            while (index <= loopCounter) {
                value = it.next();
                index++;
            }
            setLoopVariable(execution, collectionElementVariable, value);
        }

        if (loopCounter == 0) {
            innerActivityBehavior.execute(execution);
        } else {
            execution.executeActivity(activity);
        }
    }

    protected boolean usesCollection() {
        return collectionExpression != null
                || collectionVariable != null;
    }

    protected boolean isExtraScopeNeeded() {
        return innerActivityBehavior instanceof SubProcessActivityBehavior;
    }

    protected int resolveLoopCardinality(ActivityExecution execution) {
        // Using Number since expr can evaluate to eg. Long (which is also the default for Juel)
        Object value = loopCardinalityExpression.getValue(execution);
        if (value instanceof Number) {
            return ((Number) value).intValue();
        } else if (value instanceof String) {
            return Integer.valueOf((String) value);
        } else {
            throw new ActivityIllegalArgumentException("Could not resolve loopCardinality expression '"
                    + loopCardinalityExpression.getExpressionText() + "': not a number nor number String");
        }
    }

    protected boolean completionConditionSatisfied(ActivityExecution execution) {
        if (completionConditionExpression != null) {
            Object value = completionConditionExpression.getValue(execution);
            if (!(value instanceof Boolean)) {
                throw new ActivityIllegalArgumentException("completionCondition '"
                        + completionConditionExpression.getExpressionText()
                        + "' does not evaluate to a boolean value");
            }
            Boolean booleanValue = (Boolean) value;
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Completion condition of multi-instance satisfied: {}", booleanValue);
            }
            return booleanValue;
        }
        return false;
    }

    protected void setLoopVariable(ActivityExecution execution, String variableName, Object value) {
        execution.setVariableLocal(variableName, value);
    }

    protected Integer getLoopVariable(ActivityExecution execution, String variableName) {
        Object value = execution.getVariableLocal(variableName);
        ActivityExecution parent = execution.getParent();
        while (value == null && parent != null) {
            value = parent.getVariableLocal(variableName);
            parent = parent.getParent();
        }
        return (Integer) value;
    }

    protected Integer getLocalLoopVariable(ActivityExecution execution, String variableName) {
        return (Integer) execution.getVariableLocal(variableName);
    }

    protected void callActivityEndListeners(ActivityExecution execution) {
        List<ExecutionListener> listeners = activity.getExecutionListeners(PvmEventConstacts.EVENTNAME_END);
        CallActivityEndListenersOperation atomicOperation = new CallActivityEndListenersOperation(listeners);
        // Context.getCommandContext().performOperation(atomicOperation, (InterpretableExecution) execution);
    }

    protected void logLoopDetails(ActivityExecution execution, String custom, int loopCounter,
                                  int nrOfCompletedInstances, int nrOfActiveInstances, int nrOfInstances) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Multi-instance '{}' {}. Details: loopCounter={}, nrOrCompletedInstances={},nrOfActiveInstances={},nrOfInstances={}",
                    execution.getActivity(), custom, loopCounter, nrOfCompletedInstances, nrOfActiveInstances, nrOfInstances);
        }
    }


    public Expression getLoopCardinalityExpression() {
        return loopCardinalityExpression;
    }

    public void setLoopCardinalityExpression(Expression loopCardinalityExpression) {
        this.loopCardinalityExpression = loopCardinalityExpression;
    }

    public Expression getCompletionConditionExpression() {
        return completionConditionExpression;
    }

    public void setCompletionConditionExpression(Expression completionConditionExpression) {
        this.completionConditionExpression = completionConditionExpression;
    }

    public Expression getCollectionExpression() {
        return collectionExpression;
    }

    public void setCollectionExpression(Expression collectionExpression) {
        this.collectionExpression = collectionExpression;
    }

    public String getCollectionVariable() {
        return collectionVariable;
    }

    public void setCollectionVariable(String collectionVariable) {
        this.collectionVariable = collectionVariable;
    }

    public String getCollectionElementVariable() {
        return collectionElementVariable;
    }

    public void setCollectionElementVariable(String collectionElementVariable) {
        this.collectionElementVariable = collectionElementVariable;
    }

    public String getCollectionElementIndexVariable() {
        return collectionElementIndexVariable;
    }

    public void setCollectionElementIndexVariable(String collectionElementIndexVariable) {
        this.collectionElementIndexVariable = collectionElementIndexVariable;
    }

    public void setInnerActivityBehavior(AbstractBpmnActivityBehavior innerActivityBehavior) {
        this.innerActivityBehavior = innerActivityBehavior;
        this.innerActivityBehavior.setMultiInstanceActivityBehavior(this);
    }

    public AbstractBpmnActivityBehavior getInnerActivityBehavior() {
        return innerActivityBehavior;
    }


    private static final class CallActivityEndListenersOperation implements AtomicOperation {

        private List<ExecutionListener> listeners;

        private CallActivityEndListenersOperation(List<ExecutionListener> listeners) {
            this.listeners = listeners;
        }

        @Override
        public void execute(InterpretableExecution execution) {
            for (ExecutionListener executionListener : listeners) {
                try {
                    DelegateInvocation invocation = new ExecutionListenerInvocation(executionListener, execution);
                    invocation.proceed();
                } catch (Exception e) {
                    throw new ActivityException("Couldn't execute end listener", e);
                }
            }
        }

        @Override
        public boolean isAsync(InterpretableExecution execution) {
            return false;
        }

    }
}
